package kercar.device;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import kercar.compass.Compass;
import kercar.debug.Debugger;
import kercar.gps.GPSPosition;
import kercar.manager.CompassManager;
import kercar.manager.GPSManager;
import kercar.manager.MailManager;
import kercar.manager.MapManager;
import kercar.manager.MovementManager;
import kercar.manager.PhotoManager;
import kercar.manager.WebManager;
import kercar.route.Route;

public class MasterChief extends UnicastRemoteObject implements RemoteDevice {

	/************************************************************************* BLOCK
	 * ATTRIBUTES
	 *******************************************************************************/

	private static final long serialVersionUID = 1L;
	private static final int SPEED = 100;
	private static final double COMPASS_GAP = 1,	// 1 degree gap
								GPS_GAP = 0.00003;	// 3 meters gap

	// leader thread
	private Leader _leader;

	// managers
	private MovementManager _movManager;
	private GPSManager _gpsManager;
	private CompassManager _compManager;
	private MapManager _mapManager;
	private PhotoManager _photoManager;
	private WebManager _webManager;
	private MailManager _mailManager;
	
	private boolean _takePhoto, _isRunning = true;
	private String _mail;

	/************************************************************************* BLOCK
	 * CONSTRUCTOR
	 *******************************************************************************/

	public MasterChief() throws RemoteException {
	}

	/************************************************************************* BLOCK
	 * REMOTE DEVICE METHODS
	 *******************************************************************************/

	@Override
	public void startRide(GPSPosition destination, boolean takePhoto, String mail) throws RemoteException {
		_takePhoto = takePhoto;
		_mail = mail;

		Debugger.logMsg("Starting ride { destination : " + destination + "; takePhoto : " + _takePhoto + "; mail : " + _mail + " }");
		
		GPSPosition currentPosition = _gpsManager.getPosition();
		
		while (currentPosition.getLatitude() == 0 && currentPosition.getLongitude() == 0) {
			try {
				Debugger.logErr("Robot GPS is not localized");
				currentPosition = _gpsManager.getPosition();
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				Debugger.logException(e);
			}
		}
		
		_mapManager.addRoute(MapManager.computeRoute(_gpsManager.getPosition(), destination));
		_mapManager.addRoute(MapManager.computeRoute(destination, _gpsManager.getPosition()));
		
		_isRunning = true;
		
		_leader = new Leader();
		_leader.setRoute(_mapManager.getCurrentRoute());
		_leader.start();
	}

	@Override
	public void cancelRide() throws RemoteException {
		stopMoving();
		_isRunning = false;
	}

	@Override
	public boolean isArrived() throws RemoteException {
		return Math.abs(_mapManager.getCurrentRoute().getDestination().getLongitude() - _gpsManager.getPosition().getLongitude()) < GPS_GAP
			&& Math.abs(_mapManager.getCurrentRoute().getDestination().getLatitude() - _gpsManager.getPosition().getLatitude()) < GPS_GAP;
	}

	@Override
	public void moveForward(double speed) throws RemoteException {
		_movManager.moveForward(speed);
	}

	@Override
	public void moveBackward(double speed) throws RemoteException {
		_movManager.moveBackward(speed);
	}

	@Override
	public void turn(int angle) throws RemoteException {
		_movManager.turn(angle);
	}

	@Override
	public void stopMoving() throws RemoteException {
		_movManager.stopMoving();
	}

	/************************************************************************* BLOCK
	 * GETTERS & SETTERS
	 *******************************************************************************/

	@Override
	public GPSPosition getPosition() throws RemoteException {
		return _gpsManager.getPosition();
	}

	@Override
	public Compass getCompass() throws RemoteException {
		return _compManager.getCompass();
	}

	public void setGPSManager(GPSManager gpsManager) {
		_gpsManager = gpsManager;
	}

	public void setCompassManager(CompassManager compManager) {
		_compManager = compManager;
	}

	public void setMovementManager(MovementManager movManager) {
		_movManager = movManager;
	}

	public void setMapManager(MapManager mapManager) {
		_mapManager = mapManager;
	}
	
	public void setPhotoManager(PhotoManager photoManager) {
		_photoManager = photoManager;
	}
	
	public void setConnectToWeb(WebManager connectToWeb) {
		_webManager = connectToWeb;
	}
	
	public void setMailManager(MailManager mailManager) {
		_mailManager = mailManager;
	}

	/************************************************************************* BLOCK
	 * PRIVATE
	 *******************************************************************************/

	private void destinationReached() throws RemoteException {
		Debugger.logMsg("Destination has been reached");

		stopMoving();

		_isRunning = false;
		_leader.interrupt();
		
		if (_takePhoto) _photoManager.takePhoto();
		
		if (_photoManager.getNbPhoto() != 0) {
			if (_webManager.isConnected()) {
				int nbPhoto=_photoManager.getNbPhoto();
				
				for (int i = 0; i < nbPhoto; i++)
					_mailManager.send(i, _mail);
			}
			_photoManager.photoSent();
		}
		
		Route route = _mapManager.nextRoute();
		
		if (route != null) {
			_leader.setRoute(route);
			_leader.start();
		}
	}

	/************************************************************************* BLOCK
	 * INTERNAL CLASSES
	 *******************************************************************************/

	private class Leader extends Thread {

		/********************************************************************* BLOCK
		 * ATTRIBUTES
		 ***************************************************************************/
		
		private Route _route;
		private GPSPosition _currentPosition, _nextTarget;
		
		/********************************************************************* BLOCK
		 * PUBLIC METHODS
		 ***************************************************************************/
		
		public void run() {
			while (_isRunning) {
				try {
					Debugger.logMsg("Robot position : " + _gpsManager.getPosition());

					if (!isArrived()) {
						if (isNextTargetReached()) {
							_route.loadNextTarget();
						}
						
						setDirection();
						
						_movManager.moveForward(SPEED);
					} else {
						destinationReached();
					}

					Thread.sleep(1000);
				} catch (Exception e) {
					_isRunning = false;
				}
			}
		}
		
		public void setRoute(Route route) {
			_route = route;
		}
		
		/********************************************************************* BLOCK
		 * PRIVATE METHODS
		 ***************************************************************************/
		
		private void updatePositions() {
			_nextTarget = _route.getNextTarget();
			_currentPosition = _gpsManager.getPosition();
		}
		
		private double computeNewHeading() {
			updatePositions();
			
			double X = _nextTarget.getLongitude() - _currentPosition.getLongitude();
			double Y = _nextTarget.getLatitude() - _currentPosition.getLatitude();
			
			if (X >= 0)
				return Math.toDegrees(Math.acos(Y / (Math.sqrt(X * X + Y * Y))));
			
			return 360 - Math.toDegrees(Math.acos(Y / (Math.sqrt(X * X + Y * Y))));
		}

		private void setDirection() {
			double heading, newHeading;
			do {
				heading = _compManager.getCompass().getAngle();
				newHeading = computeNewHeading();
				
				if (heading > newHeading) {
					System.out.println("First IF");
					
					if (heading - newHeading < 180)
						_movManager.turn(-1);
					else
						_movManager.turn(1);
				} else {
					System.out.println("Else IF");
					if (newHeading - heading < 180) {
						//_movManager.turn(1);
						try {
							turn(1);
							System.out.println("turn(1);");
						} catch (RemoteException e) {
							Debugger.logException(e);
						}
						_movManager.turn(-1);
						System.out.println("_movManager.turn(1);");
					}
					else {
						_movManager.turn(-1);
						System.out.println("_movManager.turn(-1);");
					}
				}
				
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					Debugger.logException(e);
				}
			} while (Math.abs(heading - newHeading) > COMPASS_GAP);
			
			Debugger.logMsg("heading : " + heading + " - new heading : " + newHeading);
		}
		
		private boolean isNextTargetReached() {
			updatePositions();
			return Math.abs(_nextTarget.getLongitude() - _currentPosition.getLongitude()) < GPS_GAP
				&& Math.abs(_nextTarget.getLatitude() - _currentPosition.getLatitude()) < GPS_GAP;
		}

	}

}
